package com.xr.core.system.common.config.multiRequestBodyConfig;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.xr.core.system.common.annotation.MultiRequestBody;
import com.xr.core.system.common.utils.JsonUtil;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.ClassUtils;
import org.springframework.util.StreamUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;

import javax.servlet.ServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * MultiRequestBody解析器 解决的问题： 1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收； 2、多个对象需要封装到一个对象里才可以用@RequestBody接收。 主要优势：
 * 1、支持通过注解的value指定JSON的key来解析对象。 2、支持通过注解无value，直接根据参数名来解析对象 3、支持基本类型的注入 4、支持GET和其他请求方式注入
 * 5、支持通过注解无value且参数名不匹配JSON串key时，根据属性解析对象。 6、支持多余属性(不解析、不报错)、支持参数“共用”（不指定value时，参数名不为JSON串的key）
 * 7、支持当value和属性名找不到匹配的key时，对象是否匹配所有属性。
 */
public class MultiRequestBodyArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

    private static final String JSON_BODY_ATTRIBUTE = "JSON_REQUEST_BODY";

    public MultiRequestBodyArgumentResolver(List<HttpMessageConverter<?>> converters) {
        super(converters);
    }

    public MultiRequestBodyArgumentResolver(List<HttpMessageConverter<?>> converters, List<Object> requestResponseBodyAdvice) {
        super(converters, requestResponseBodyAdvice);
    }

    /**
     * 设置支持的方法参数类型
     *
     * @param parameter 方法参数
     * @return 支持的类型
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 支持带@MultiRequestBody注解的参数
        return parameter.hasParameterAnnotation(MultiRequestBody.class);
    }

    /**
     * 参数解析，利用jackson 注意：非基本类型返回null会报空指针异常，要通过反射或者JSON工具类创建一个空对象
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
        String name = parameter.getParameterName();
        WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
        if (arg != null) {
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
            }
        }
        mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
        return adaptArgumentIfNecessary(arg, parameter);
    }

    @Override
    protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
                                                   Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
        ServletServerHttpRequest httpRequest = (ServletServerHttpRequest) inputMessage;
        String body = getRequestBody(httpRequest.getServletRequest());
        Object object = getParameterValue(JsonUtil.string2Bean(body, Map.class, String.class, Object.class), parameter, targetType);
        String string = ObjectUtil.isEmpty(object) ? null : JsonUtil.objectToJson(object);
        MultiHttpServletRequestWrapper wrapper = new MultiHttpServletRequestWrapper(httpRequest.getServletRequest(),
                string);
        Object arg = super.readWithMessageConverters(new ServletServerHttpRequest(wrapper), parameter, targetType);
        if (arg == null) {
            if (checkRequired(parameter)) {
                throw new HttpMessageNotReadableException(
                        "Required request body is missing: " + Objects.requireNonNull(parameter.getMethod()).toGenericString(), inputMessage);
            }
        }
        return arg;
    }

    protected boolean checkRequired(MethodParameter parameter) {
        return (Objects.requireNonNull(parameter.getParameterAnnotation(MultiRequestBody.class)).required() && !parameter.isOptional());
    }

    protected Object getParameterValue(Map<String, Object> map, MethodParameter parameter, Type paramType) {
        // 根据@MultiRequestBody注解value作为json解析的key
        MultiRequestBody parameterAnnotation = parameter.getParameterAnnotation(MultiRequestBody.class);
        String defaultValue = parameterAnnotation.defaultValue();
        if (map != null && !map.isEmpty()) {
            // 注解的value是JSON的key
            // 注解为设置value则用参数名当做json的key
            String key = StrUtil.isNotEmpty(parameterAnnotation.value()) ? parameterAnnotation.value()
                    : parameter.getParameterName();
            // 如果@MultiRequestBody注解没有设置value，则取参数名FrameworkServlet作为json解析的key
            Object value = map.get(key);
            // 如果设置了value但是解析不到，报错
            if (ObjectUtil.isEmpty(value) && ObjectUtil.isNotEmpty(defaultValue)) {
                value = defaultValue;
            }
            // 通过注解的value或者参数名解析，能拿到value进行解析
            if (value == null) {
                if (parameterAnnotation.parseAllFields()) {
                    // 非基本类型，允许解析，将外层属性解析
                    if (!ClassUtils.isPrimitiveOrWrapper(parameter.getParameterType())
                            && parameter.getParameterType() != Date.class
                            && parameter.getParameterType() != String.class
                            && !Date.class.isAssignableFrom(parameter.getParameterType())
                            && !Temporal.class.isAssignableFrom(parameter.getParameterType())) {
                        value = JsonUtil.map2pojo(map, paramType);
                    }
                }
            }
            return value;
        } else {
            return ObjectUtil.isEmpty(defaultValue) ? null : defaultValue;
        }
    }

    protected String getRequestBody(ServletRequest request) {
        // 有就直接获取
        String body = (String) request.getAttribute(JSON_BODY_ATTRIBUTE);
        // 没有就从请求中读取
        if (body == null) {
            try {
                body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
                request.setAttribute(JSON_BODY_ATTRIBUTE, body);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return body;
    }

}
